home *** CD-ROM | disk | FTP | other *** search
/ Total Network Tools 2002 / NextStepPublishing-TotalNetworkTools2002-Win95.iso / Archive / Misc Servers / Zope.exe / PCGIFILE.PY < prev    next >
Encoding:
Python Source  |  2000-09-07  |  19.9 KB  |  499 lines

  1. #!/usr/local/bin/python
  2. # pcgifile.py - pcgi info file sanity testing - jeffbauer@bigfoot.com
  3. # Copyright(c) 1998, Jeff Bauer
  4.  
  5. # 0.6a  August 9, 1998
  6. #   - added socket checking
  7. #
  8. # 0.5a  August 7, 1998
  9. #   - added NT compatibility
  10. #   - improved import checking
  11. #
  12. # 0.4a  July 27, 1998 
  13. #   - added checks for executable permissions
  14. #   - print versions of relevant modules
  15.  
  16. __version__ = "0.6a"
  17. Delimiter = '='
  18.  
  19. # no class-based exceptions due to 1.4 compatbility
  20. PcgiFileException='PcgiFileException' 
  21.  
  22. class PcgiFile:
  23.     def __init__(self, pcgifile):
  24.         self.pcgifile = pcgifile
  25.         self.infodict = {}
  26.         self.combined = {}
  27.         self.log = []
  28.         self.resource = []
  29.         self.file_contents = []
  30.         self.module_version = []
  31.         self.continue_testing = 1
  32.         self.pcgi_wrapper = None
  33.         try:
  34.             self.readInfoFile()
  35.             self.checkRequiredKeys()
  36.             self.checkPCGIValues()
  37.             self.lookupPCGIPublisher()
  38.             self.checkWritePermissions()
  39.             self.checkImports()
  40.             self.checkSockets()
  41.         except PcgiFileException:
  42.             self.continue_testing = 0
  43.  
  44.     def checkImports(self):
  45.         try:
  46.             from cgi_module_publisher import publish_module
  47.         except ImportError:
  48.             self.log.append("error attempting: 'from cgi_module_publisher import publish_module'")
  49.             raise PcgiFileException
  50.  
  51.         try:
  52.             import cgi_module_publisher, CGIResponse
  53.             self.module_version.append("%-20s %-7s  %s" % \
  54.                                        ('cgi_module_publisher',
  55.                                         cgi_module_publisher.__version__,
  56.                                         cgi_module_publisher.__file__) )
  57.             self.module_version.append("%-20s %-7s  %s" % \
  58.                                        ('CGIResponse',
  59.                                         CGIResponse.__version__,
  60.                                         CGIResponse.__file__) )
  61.             self.module_version.append("%-20s %-7s  %s" % 
  62.                                        ('pcgifile',
  63.                                         __version__,
  64.                                         sys.argv[0]) )
  65.             self.module_version.append("%-20s %-7s  %s" % \
  66.                                        ('pcgi-wrapper',
  67.                                         self.getPcgiWrapperVersion(),
  68.                                         self.pcgi_wrapper))
  69.         except ImportError:
  70.             pass
  71.         except NameError:
  72.             pass
  73.  
  74.         try:
  75.             import pcgi_publisher
  76.         except ImportError:
  77.             if self.combined.has_key('PCGI_PUBLISHER'):
  78.                 d, s = os.path.split(self.combined['PCGI_PUBLISHER'])
  79.                 if not d in sys.path:
  80.                     sys.path.append(d)
  81.         try:
  82.             import pcgi_publisher
  83.             self.module_version.append("%-20s %-7s  %s" % \
  84.                                        ('pcgi_publisher',
  85.                                         pcgi_publisher.__version__,
  86.                                         pcgi_publisher.__file__))
  87.         except ImportError:
  88.             pass
  89.         except NameError:
  90.             pass
  91.  
  92.     def checkPCGIValues(self):
  93.         if self.combined.has_key('PCGI_EXE'):
  94.             sw_exe = self.combined['PCGI_EXE']
  95.         else:
  96.             sw_exe = sys.executable
  97.             self.log.append("advisory recommendation: specify PCGI_EXE=%s" % \
  98.                             sys.executable)
  99.         if os.path.isfile(sw_exe):
  100.             self.resource.append("Executable:\t%s" % sw_exe)
  101.         else:
  102.             self.log.append("executable not found: %s" % sw_exe)
  103.             raise PcgiFileException
  104.         if self.combined.has_key('PCGI_PID_FILE'):
  105.             f = self.combined['PCGI_PID_FILE']
  106.             d = os.path.split(f)[0]
  107.             if os.path.isdir(d):
  108.                 self.resource.append("PID file:\t%s" % f)
  109.             else:
  110.                 self.log.append("directory not found: %s" % d)
  111.                 raise PcgiFileException
  112.         if self.combined.has_key('PCGI_SOCKET_FILE'):
  113.             f = self.combined['PCGI_SOCKET_FILE']
  114.             d = os.path.split(f)[0]
  115.             if os.path.isdir(d):
  116.                 self.resource.append("Socket file:\t%s" % f)
  117.             else:
  118.                 self.log.append("directory not found: %s" % d)
  119.                 raise PcgiFileException
  120.         if not self.combined.has_key('PCGI_NAME'):
  121.             self.log.append("advisory recommendation: specify PCGI_NAME")
  122.         if self.combined.has_key('PCGI_MODULE_PATH'):
  123.             p = self.combined['PCGI_MODULE_PATH']
  124.             if os.path.isfile(p):
  125.                 self.resource.append("Module:\t%s" % p)
  126.             else:
  127.                 self.log.append("module not found: %s" % p)
  128.                 raise PcgiFileException
  129.         if self.combined.has_key('PCGI_ERROR_LOG'):
  130.             self.resource.append("Error Log:\t%s" % \
  131.                                  self.combined['PCGI_ERROR_LOG'])
  132.         if self.combined.has_key('PCGI_WORKING_DIR'): # deprecated
  133.             d = self.combined['PCGI_WORKING_DIR']
  134.             if os.path.isfile(d):
  135.                 self.resource.append("Working Directory:\t%s" % d)
  136.             else:
  137.                 self.log.append("working directory not found: %s" % d)
  138.                 raise PcgiFileException
  139.  
  140.     def checkRequiredKeys(self):
  141.         """
  142.         Check for the required PCGI keys.
  143.         """
  144.         for (k,v) in os.environ.items():
  145.             self.combined[k] = v
  146.         for (k,v) in self.infodict.items():
  147.             if self.combined.has_key(k):
  148.                 self.log.append("%s=%s, overwrites: %s" % (k, v, self.combined[k]))
  149.             self.combined[k] = v
  150.         for k in ['PCGI_PID_FILE','PCGI_SOCKET_FILE','PCGI_MODULE_PATH']:
  151.             if not self.combined.has_key(k):
  152.                 self.log.append("missing parameter: %s" % k)
  153.                 raise PcgiFileException
  154.         # PCGI_INFO_FILE is assigned by the pcgi-wrapper, so that it
  155.         # may be known (made available) to pcgi_publisher.
  156.         self.combined['PCGI_INFO_FILE'] = self.pcgifile
  157.  
  158.     def checkSockets(self):
  159.         """
  160.         Check for possible socket-related error conditions.
  161.         """
  162.         try:
  163.             import socket
  164.         except ImportError:
  165.             self.log.append("unable to import socket module")
  166.             raise PcgiFileException
  167.  
  168.         port = None
  169.         if self.combined.has_key('PCGI_PORT'):
  170.             try:
  171.                 port = string.atoi(self.combined['PCGI_PORT'])
  172.             except ValueError:
  173.                 self.log.append("invalid port '%s', PCGI_PORT must be an integer" % self.combined['PCGI_PORT'])
  174.                 raise PcgiFileException
  175.         if os.name == 'posix':
  176.             if port:
  177.                 self.log.append("cannot specify PCGI_PORT directive on Unix - no support for INET sockets")
  178.                 raise PcgiFileException
  179.         elif not port:
  180.             self.log.append("win32 platform must specify missing PCGI_PORT directive (default=7244)");
  181.             raise PcgiFileException
  182.  
  183.         if port:
  184.             if self.combined.has_key('PCGI_HOST'):
  185.                 hostname = self.combined['PCGI_HOST']
  186.                 if hostname != socket.gethostname():
  187.                     self.log.append("advisory recommendation: PCGI_HOST '%s' doesn't match '%s'" % (hostname, socket.gethostname()))
  188.             else:
  189.                 hostname = socket.gethostname()
  190.  
  191.         if port:
  192.             try:
  193.                 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  194.                 sock.bind((hostname, port))
  195.             except socket.error:
  196.                 self.log.append("error creating/binding INET socket (%s, %s)" % (hostname, port))
  197.                 raise PcgiFileException
  198.         else:
  199.             try:
  200.                 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  201.             except socket.error:
  202.                 self.log.append("error creating UNIX socket")
  203.                 raise PcgiFileException
  204.  
  205.             socketFile = self.combined.get('PCGI_SOCKET_FILE')
  206.             if os.path.exists(socketFile):
  207.                 self.log.append("advisory: socket %s in use, bind() not tested" % socketFile)
  208.             else:
  209.                 try:
  210.                     sock.bind(socketFile)
  211.                     os.unlink(socketFile)
  212.                 except socket.error:
  213.                     self.log.append("error binding UNIX socket (%s)" % socketFile)
  214.                     raise PcgiFileException
  215.  
  216.     def checkWritePermissions(self):
  217.         """
  218.         Check write permissions for PCGI_SOCKET_FILE, PCGI_PID_FILE, and
  219.         (if specified) PCGI_ERROR_LOG.
  220.         """
  221.         fn = {}
  222.         if self.combined.has_key('PCGI_PID_FILE'):
  223.             fn['PCGI_PID_FILE'] = self.combined['PCGI_PID_FILE']
  224.         if self.combined.has_key('PCGI_SOCKET_FILE'):
  225.             fn['PCGI_SOCKET_FILE'] = self.combined['PCGI_SOCKET_FILE']
  226.         if self.combined.has_key('PCGI_ERROR_LOG'):
  227.             fn['PCGI_ERROR_LOG'] = self.combined['PCGI_ERROR_LOG']
  228.         for key, file in fn.items():
  229.             if os.path.exists(file):
  230.                 try:
  231.                     f = open(file,'a+')
  232.                     f.close()
  233.                 except IOError:
  234.                     self.log.append("%s write permission error: %s" % \
  235.                                     (key, file))
  236.                     raise PcgiFileException
  237.             else:
  238.                 path = os.path.split(file)[0]
  239.                 import tempfile
  240.                 tempfile.tempdir = path
  241.                 tmpfile = tempfile.mktemp()
  242.                 try:
  243.                     f = open(tmpfile,'w+')
  244.                     f.close()
  245.                     os.unlink(tmpfile)
  246.                 except IOError:
  247.                     self.log.append("%s write permission error: %s" % \
  248.                                     (key, file))
  249.                     raise PcgiFileException
  250.  
  251.     def environList(self):
  252.         """
  253.         return a sorted list of how the environment would likely appear 
  254.         if run through the pcgi-wrapper.
  255.         """
  256.         e = []
  257.         keys = self.combined.keys()
  258.         keys.sort()
  259.         for k in keys:
  260.             e.append("%s\t%s" % (k, self.combined[k]))
  261.         return e
  262.  
  263.     def getPcgiWrapperVersion(self):
  264.         """
  265.         Execute pcgi-wrapper with no arguments and grab the version id.
  266.         """
  267.         try:
  268.             import tempfile
  269.             tmpfile = tempfile.mktemp()
  270.             os.system("%s > %s" % (self.pcgi_wrapper, tmpfile))
  271.             f = open(tmpfile, 'r')
  272.             r = f.readlines()
  273.             f.close()
  274.             os.unlink(tmpfile)
  275.             for l in r:
  276.                 s = string.strip(l)
  277.                 if s[:21] == 'pcgi-wrapper-version ':
  278.                     return string.split(s)[1]
  279.         except ImportError:
  280.             pass
  281.         return None
  282.  
  283.     def isexecutable(self, path, real=None):
  284.         if os.name == 'posix':
  285.             return self.pathperm(path, real)[2]
  286.         else:
  287.             return 1
  288.  
  289.     def lookupPCGIPublisher(self):
  290.         """
  291.         The most efficient way for pcgi-wrapper to determine which
  292.         pcgi_publisher to use is for the pcgi info file to specify
  293.         it with the PCGI_PUBLISHER directive.  Using the PCGI_PUBLISHER
  294.         is arguably the *best* method, as pcgi-wrapper will find it
  295.         quicker than otherwise.  Still, in the interest of flexibility,
  296.         pcgi-wrapper will attempt to locate pcgi_publisher using the
  297.         following rules:
  298.  
  299.         1.  PCGI_PUBLISHER  (*best*)
  300.  
  301.         Rules 2-5, look in the paths below for files named: pcgi_publisher.py,
  302.           pcgi_publisher.pyc, pcgi_publisher.pyo, pcgi_publisher.
  303.         2.  PCGI_INSERT_PATH, if available
  304.         3.  PYTHONPATH, if available
  305.         4.  Look in the directory of PCGI_MODULE_PATH
  306.         5.  Look in the directory of the pcgi info file
  307.         """
  308.         if self.combined.has_key('PCGI_PUBLISHER'):
  309.             p = self.combined['PCGI_PUBLISHER']
  310.             if os.path.isfile(p):
  311.                 self.resource.append("Publisher:\t%s" % p)
  312.             else:
  313.                 self.log.append("publisher not found: %s" % p)
  314.                 raise PcgiFileException
  315.             return
  316.         self.log.append("advisory recommendation: specify PCGI_PUBLISHER")
  317.         # search through combined PCGI_INSERT_PATH + PYTHONPATH directories
  318.         searchPath = ""
  319.         if self.combined.has_key('PCGI_INSERT_PATH'):
  320.             searchPath = searchPath + self.combined['PCGI_INSERT_PATH']
  321.         if self.combined.has_key('PYTHONPATH'):
  322.             searchPath = searchPath + self.combined['PYTHONPATH']
  323.         publisherName = ['pcgi_publisher.py','pcgi_publisher.pyc','pcgi_publisher.pyo','pcgi_publisher']
  324.         for d in string.split(searchPath, ':'):
  325.             for p in publisherName:
  326.                 pcgiPublisher = "%s%s%s" % (d, os.sep, p)
  327.                 if os.path.isfile(pcgiPublisher):
  328.                     self.resource.append("Publisher:\t%s" % pcgiPublisher)
  329.                     return
  330.         # look in module directory
  331.         if self.combined.has_key('PCGI_MODULE_PATH'):
  332.             (d, x) = os.path.split(self.combined['PCGI_MODULE_PATH'])
  333.             for p in publisherName:
  334.                 pcgiPublisher = "%s%s%s" % (d, os.sep, p)
  335.                 if os.path.isfile(pcgiPublisher):
  336.                     self.resource.append("Publisher:\t%s" % pcgiPublisher)
  337.                     return
  338.         # look in pcgi info file directory
  339.         (d, x) = os.path.split(self.pcgifile)
  340.         for p in publisherName:
  341.             pcgiPublisher = "%s%s%s" % (d, os.sep, p)
  342.             if os.path.isfile(pcgiPublisher):
  343.                 self.resource.append("Publisher:\t%s" % pcgiPublisher)
  344.                 return
  345.         self.log.append("Unable to locate the pcgi_publisher")
  346.         raise PcgiFileException
  347.  
  348.     def pathperm(self, path, real=None):
  349.         """
  350.         Returns a 3-tuple of booleans indicating whether the process has
  351.         (read, write, execute) permission.  A true value for the 'real'
  352.         argument indicates the test should occur for the real id rather
  353.         than the effective id.
  354.         """
  355.         stat = os.stat(path)
  356.         if real is None:
  357.             uid = os.geteuid()
  358.             gid = os.getegid()
  359.         else:
  360.             uid = os.getuid()
  361.             gid = os.getgid()
  362.  
  363.         if uid == stat[4]:
  364.             return (00400 & stat[0], 00200 & stat[0], 00100 & stat[0])
  365.         elif gid == stat[5]:
  366.             return (00040 & stat[0], 00020 & stat[0], 00010 & stat[0])
  367.         else:
  368.             return (00004 & stat[0], 00002 & stat[0], 00001 & stat[0])
  369.  
  370.     def readInfoFile(self):
  371.         max_directives = 12  # arbitrary number defined in pcgi.h
  372.         if not os.path.isfile(self.pcgifile):
  373.             self.log.append("unable to locate file: %s" % self.pcgifile)
  374.             raise PcgiFileException
  375.         elif not self.isexecutable(self.pcgifile):
  376.             self.log.append("info file '%s' not executable" % self.pcgifile)
  377.             raise PcgiFileException
  378.         f = open(self.pcgifile, 'r')
  379.         lc = 0
  380.         for r in f.readlines():
  381.             lc = lc + 1
  382.             s = string.strip(r)
  383.             self.file_contents.append(s)
  384.             if lc == 1:
  385.                 if s[:2] != '#!':
  386.                     self.log.append("first line missing header, e.g. #!/usr/local/bin/pcgi-wrapper")
  387.                     raise PcgiFileException
  388.                 else:
  389.                     self.pcgi_wrapper = string.strip(s[2:])
  390.                     if not os.path.isfile(self.pcgi_wrapper):
  391.                         self.log.append("unable to find wrapper: %s" % \
  392.                                         self.pcgi_wrapper)
  393.                         raise PcgiFileException
  394.                     elif not self.isexecutable(self.pcgi_wrapper):
  395.                         self.log.append("wrapper '%s' is not executable" % \
  396.                                         self.pcgi_wrapper)
  397.                         raise PcgiFileException
  398.  
  399.             if len(s) < 1 or s[0] == '#':
  400.                 continue
  401.             pos = string.find(s, Delimiter)
  402.             if pos < 0:
  403.                 self.log.append("missing '%s' delimiter at line %d: %s" % \
  404.                                 (Delimiter, lc, s))
  405.             else:
  406.                 self.infodict[string.strip(s[0:pos])] = string.strip(s[pos+1:])
  407.         f.close()
  408.         if len(self.infodict.keys()) > max_directives:
  409.             self.log.append("info fileexceeds maximum (%d) number of directives" % max_directives)
  410.             raise PcgiFileException
  411.  
  412. class PcgiFileTest:
  413.     """
  414.     CGI sanity check of the pcgi info file.
  415.     """
  416.     def __init__(self):
  417.         fs = cgi.FieldStorage()
  418.         infofile = None
  419.         if fs.has_key('infofile'):
  420.             infofile = fs['infofile'].value
  421.         elif fs.has_key('filename'):
  422.             infofile = fs['filename'].value
  423.  
  424.         if infofile is None:
  425.             print "Please specify the pcgi info file in the following manner:"
  426.             print "<pre>"
  427.             print "  http://.../cgi-bin/pcgifile.py?<STRONG>infofile=</STRONG><I>pcgifile</I>"
  428.             print "</pre>"
  429.             print "where <I>pcgifile</I> is the absolute path of the pcgi info file."
  430.         else:
  431.             print "<pre>"
  432.             print "<strong>Python %s</strong>" % sys.version
  433.             if os.environ.has_key('SERVER_SOFTWARE'):
  434.                 print "<strong>%s</strong>" % os.environ['SERVER_SOFTWARE']
  435.                 print
  436.             print "PCGI info file:\t%s" % infofile
  437.             pcgiFile = PcgiFile(infofile)
  438.             print "PCGI wrapper:\t%s" % pcgiFile.pcgi_wrapper
  439.             for m in pcgiFile.log:
  440.                 print m
  441.             if not pcgiFile.continue_testing:
  442.                 print "status: FAILURE"
  443.                 print
  444.                 print "<STRONG>%s</STRONG>" % infofile
  445.                 for r in pcgiFile.file_contents:
  446.                     print "  %s" % r
  447.             else:
  448.                 print "looks OK"
  449.                 print
  450.                 print "<STRONG>%s</STRONG>" % infofile
  451.                 for r in pcgiFile.file_contents:
  452.                     print "  %s" % r
  453.                 print
  454.                 print "<STRONG>Likely publisher resource values:</STRONG>"
  455.                 for r in pcgiFile.resource:
  456.                     print "  %s" % r
  457.                 print
  458.                 print "<STRONG>Versions of modules used:</STRONG>"
  459.                 for r in pcgiFile.module_version:
  460.                     print "  %s" % r
  461.                 print
  462.                 print "<STRONG>Resulting environment will probably appear to the publisher as:</STRONG>"
  463.                 for e in pcgiFile.environList():
  464.                     print "  %s" % e
  465.  
  466. def test():
  467.     usage = 'usage: pcgifile pcgi_info_file'
  468.     if len(sys.argv) < 2:
  469.         print usage
  470.         sys.exit(1)
  471.     infoFile = sys.argv[1]
  472.     pcgiFile = PcgiFile(infoFile)
  473.     for m in pcgiFile.log:
  474.         print m
  475.     if pcgiFile.continue_testing:
  476.         print "%s looks OK" % infoFile
  477.  
  478. if __name__ == '__main__':
  479.     try:
  480.         import cgi, os, sys, string, traceback
  481.         if os.environ.has_key('SERVER_PROTOCOL'):
  482.             print "Content-type: text/html"
  483.             print "Expires: Monday, 1-Jan-96 00:00:00 GMT"
  484.             print "Pragma: no-cache"
  485.             print
  486.             sys.stderr = sys.stdout
  487.             try:
  488.                 pcgiFileTest = PcgiFileTest()
  489.             except:
  490.                 print "<pre>"
  491.                 traceback.print_exc()
  492.         else:
  493.             test()
  494.     except ImportError:
  495.         print "Content-type: text/html"
  496.         print
  497.         print "error during python imports; to fix try adding to pcgifile.py: <br><pre>"
  498.         print "    sys.path[0:0] = [path1, path2, ...]"
  499.